/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.net.Socket;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.Calendar;
import java.util.GregorianCalendar;

import nfse.query.QueryDate;
import nfse.query.Time;

/*******************************************************************************
 * 
 * This is a convenience class for use in various NetFSE classes.
 * 
 * @author
 * 
 * This code is the property of Los Alamos National Laboratory and the
 * University of California, Copyright 2005
 */
public class NetFSEUtil {

    private static char[] toReplace = { '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007',
            '\u0008', '\u000b', '\u000c', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014',
            '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', '\u001e',
            '\u001f' };

    public static int getProtocol(String protoStr) {
        protoStr = protoStr.toUpperCase();
        if (protoStr.equals("TCP"))
            return 6;
        else if (protoStr.equals("UDP"))
            return 17;
        else if (protoStr.equals("ICMP"))
            return 1;
        return -1;
    }

    /***************************************************************************
     * Encodes a String with the correct encoding for XML.
     * 
     * @param str
     *            The String to XML encode
     * @return The XML-encoded version of the String passed in
     */
    public static String xmlify(String str) {
        int idx = 0;

        while (idx < str.length()) {
            char c = str.charAt(idx);
            switch (c) {
            case '>':
                str = str.substring(0, idx) + "&gt;" + str.substring(idx + 1);
                idx += 4;
                break;
            case '<':
                str = str.substring(0, idx) + "&lt;" + str.substring(idx + 1);
                idx += 4;
                break;
            case '&':
                str = str.substring(0, idx) + "&amp;" + str.substring(idx + 1);
                idx += 5;
                break;
            /*
             * case ' ' : str = str.substring(0, idx) + "&nbsp;" +
             * str.substring(idx + 1); idx += 6; break;
             */
            case '"':
                str = str.substring(0, idx) + "&quot;" + str.substring(idx + 1);
                idx += 6;
                break;
            case '\'':
                str = str.substring(0, idx) + "&apos;" + str.substring(idx + 1);
                idx += 6;
                break;
            default:
                idx++;
                break;

            }
        }
        str = NetFSEUtil.truncateUnicode(str);
        return str;
    }

    /***************************************************************************
     * Removes any unnecessary unicode characters from the String.
     * 
     * @param str
     *            The String to modify
     * @return The modified String
     */
    public static String truncateUnicode(String str) {
        String temp = str;
        int idx = str.length();
        for (int i = 0; i < toReplace.length; i++) {
            int idx2 = temp.indexOf(toReplace[i]);
            if ((idx2 != -1) && (idx2 < idx)) {
                idx = idx2;
            }
        }
        if (idx != -1) {
            return temp.substring(0, idx);
        }
        return temp;
    }

    /***************************************************************************
     * Convenience method for sending email messages according to the
     * parameters.
     * 
     * @param subject
     *            The email subject header
     * @param message
     *            The email message body
     * @param to
     *            The address to send the email.
     * @param from
     *            The address the mail is being sent from
     */
    public static void sendMail(String server, String domain, String subject, String message, String to, String from) {
        try {
            if ((to == null) || (to.length() == 0)) {
                return;

            }
            // System.out.println("Sending mail: " + subject + ", " + to + ", "
            // + from );
            Socket smtpSocket = null;
            DataOutputStream os = null;
            DataInputStream is = null;

            java.util.Date dDate = new java.util.Date();

            smtpSocket = new Socket(server, 25);
            os = new DataOutputStream(smtpSocket.getOutputStream());
            is = new DataInputStream(smtpSocket.getInputStream());
            if (smtpSocket != null && os != null && is != null) {
                // Connection was made. Socket is ready for use.
                os.writeBytes("HELO " + domain + "\r\n");
                os.writeBytes("MAIL From: " + from + "\r\n");
                os.writeBytes("RCPT To: " + to + "\r\n");
                os.writeBytes("DATA\r\n");
                os.writeBytes("X-Mailer: NetFSE\r\n");
                os.writeBytes("From: " + from + "\r\n");
                os.writeBytes("To: " + to + "\r\n");
                os.writeBytes("Subject: " + subject + "\r\n");
                os.writeBytes(message + "\r\n");
                os.writeBytes("\r\n.\r\n");
                os.writeBytes("QUIT\r\n");
            } else {
                System.err.println("Failed to connect to " + server + ".");
            }
            os.close();
            is.close();
            smtpSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /***************************************************************************
     * Convenience method for building a formatted integer string to display.
     * 
     * @param value
     *            The double to be formatted
     * @return The formatted result string
     */
    public static String formattedInteger(long value) {
        DecimalFormat format = new DecimalFormat("###,###,###,###,###");
        return format.format(value);
    }

    /***************************************************************************
     * Convenience method for building a formatted double string to display.
     * 
     * @param value
     *            The double to be formatted
     * @return The formatted result string
     */
    public static String formattedDouble(double value) {
        DecimalFormat format = new DecimalFormat("###,###,###,###,###.##");
        return format.format(value);
    }

    /***************************************************************************
     * Convenience method for removing characters from strings that will break
     * SQL insert syntax.
     * 
     * @param value
     *            The original String
     * @param maxLen
     *            The upper length bound of the record
     * @return The original string without any single or double quotes,
     *         truncated if necessary to maxLen characters
     */
    public static String prepareValueForInsert(String value, int maxLen) {
        String temp = value.replace('\'', ' ').replace('\"', ' ');
        if (temp.length() > maxLen) {
            temp = temp.substring(0, maxLen);
        }
        return temp;
    }

    /***************************************************************************
     * Given a Grok format timestamp, returns the Unix time.
     * 
     * @param value
     *            Grok timestamp to convert to Unix time
     * @return Unix time of the Grok timestamp
     * @throws NumberFormatException
     */
    public static long convertGrokTStoUnixTime(String value) throws NumberFormatException {
        String[] values = value.split("\\.");
        long temp = Long.parseLong(values[0]);
        return temp;
    }

    /***************************************************************************
     * Generic method for extracting timestamps from syslog records. Does not
     * work with all syslog configurations.
     * 
     * @param value
     *            A syslog header string
     * @return A java.sql.Timestamp representation of the syslog timestamp
     * @throws Exception
     *             Thrown when an incompatible syslog header is passed to the
     *             method
     */
    public static Timestamp timestampFromSyslog(String value) throws Exception {
        Timestamp ts = null;
        GregorianCalendar now = new GregorianCalendar();
        now.setTimeInMillis(System.currentTimeMillis());
        Timestamp nowTS = new Timestamp(now.getTimeInMillis());
        try {

            // Try 'Mon D(D) HH:MM:SS ...:'
            int idx1 = value.indexOf(" ");
            int idx2 = value.indexOf(" ", idx1 + 1);
            int idx3 = value.indexOf(" ", idx2 + 1);
            String monStr = value.substring(0, idx1);
            int semiColIdx = monStr.lastIndexOf(";");
            if (semiColIdx != -1) {
                monStr = monStr.substring(semiColIdx + 1);
            }

            String dayStr = value.substring(idx1 + 1, idx2);
            int mon = -1;
            for (int i = 0; i < NetFSE.MONTH_ABBRS.length; i++) {
                if (monStr.equals(NetFSE.MONTH_ABBRS[i])) {
                    mon = i;
                    break;
                }
            }
            String timeStr = "";
            if (idx3 != -1)
                timeStr = value.substring(idx2, idx3);
            else
                timeStr = value.substring(idx2);

            ts = Timestamp.valueOf(now.get(Calendar.YEAR) + "-" + mon + "-" + dayStr + " " + timeStr);
            if (ts.before(nowTS)) {
                ts = Timestamp.valueOf((now.get(Calendar.YEAR) - 1) + "-" + mon + "-" + dayStr + " " + timeStr);
            }
            return ts;
        } catch (Exception e) {

        }

        String[] tokens = null;
        try {
            int idx = -1;
            StringTokenizer st = new StringTokenizer(value);
            tokens = new String[st.countTokens()];
            while (st.hasMoreTokens()) {
                tokens[++idx] = st.nextToken();
            }

            int year = now.get(Calendar.YEAR);
            String[] tokens2 = tokens[0].split("-");
            int month = NetFSE.getMonthValue(tokens2[0]);
            int day = Integer.parseInt(tokens2[1]);
            ts = Timestamp.valueOf(year + "-" + (month) + "-" + day + " " + tokens[1]);
            GregorianCalendar tsCal = new GregorianCalendar();
            tsCal.setTimeInMillis(ts.getTime());
            if (tsCal.after(now)) {
                year--;
                ts = Timestamp.valueOf(year + "-" + (month) + "-" + day + " " + tokens[1]);
            }
            return ts;

        } catch (Exception e) {

        }

        try {
            ts = Timestamp.valueOf(tokens[0].substring(1) + " " + tokens[1]);
            return ts;

        } catch (Exception e) {

        }

        // throw (new Exception("Unknown timestamp format."));
        return new Timestamp(System.currentTimeMillis());
    }

    /***************************************************************************
     * Given a Webcache format timestamp, returns the Unix time.
     * 
     * @param value
     *            Webcache timestamp to convert to Unix time
     * @return Unix time of the Webcache timestamp
     * @throws NumberFormatException
     */
    public static long convertWebcacheTStoUnixTime(String value) throws NumberFormatException {
        value = value.substring(0, value.indexOf(" "));
        String token = "";
        StringTokenizer st = new StringTokenizer(value, "/");
        token = st.nextToken();
        if (token.startsWith("["))
            token = token.substring(1);
        int day = Integer.parseInt(token);
        token = st.nextToken();
        int mon = -1;
        for (mon = 0; mon < NetFSE.MONTH_ABBRS.length; mon++) {
            if (NetFSE.MONTH_ABBRS[mon].compareTo(token) == 0) {
                break;
            }
        }
        if (mon > NetFSE.MONTH_ABBRS.length - 1)
            return -1;
        token = st.nextToken();
        st = new StringTokenizer(token, ":");
        int year = Integer.parseInt(st.nextToken());
        int hr = Integer.parseInt(st.nextToken());
        int min = Integer.parseInt(st.nextToken());
        int sec = Integer.parseInt(st.nextToken());
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(Calendar.MONTH, mon);
        cal.set(Calendar.DAY_OF_MONTH, day);
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.HOUR_OF_DAY, hr);
        cal.set(Calendar.MINUTE, min);
        cal.set(Calendar.SECOND, sec);
        return cal.getTimeInMillis();
    }

    /***************************************************************************
     * Given a date and time, returns the timestamp as a hex encoded Unix time.
     * 
     * @param date
     *            The date portion of the timestamp
     * @param time
     *            The time portio of the timestamp
     * @return The timestamp as a hex encoded Unix time
     */
    public static String timeToHex(QueryDate date, Time time) {
        GregorianCalendar cal = (GregorianCalendar) Calendar.getInstance();
        cal.set(Calendar.MONTH, date.month - 1);
        cal.set(Calendar.DAY_OF_MONTH, date.day);
        cal.set(Calendar.YEAR, date.year);
        cal.set(Calendar.HOUR_OF_DAY, time.hour);
        cal.set(Calendar.MINUTE, time.minute);
        cal.set(Calendar.SECOND, time.second);
        // System.out.println("TIME: " + cal.getTime());
        return Long.toHexString(cal.getTimeInMillis() / 1000);
    }

    /***************************************************************************
     * Converts a value of some global type into its hex representation, if
     * possible.
     * 
     * @param globalType
     * @param value
     *            The value to convert to hex
     * @return The value in hex if possible or null if not
     */
    public static String convertToHex(int globalType, String value) {
        if ((globalType == NetFSE.FIELD_DST_IP) || (globalType == NetFSE.FIELD_SRC_IP)) {
            return NetFSEUtil.convertIPtoHex(value);
        } else if (globalType == NetFSE.FIELD_DST_PORT) {
            String hex = Integer.toHexString(Integer.parseInt(value));
            if (hex.length() > 4)
                hex = hex.substring(hex.length() - 4);
            while (hex.length() < 4)
                hex = "0" + hex;
            return hex;
        } else if (globalType == NetFSE.FIELD_START_TS) {
            String hex = Long.toHexString(Long.parseLong(value));
            if (hex.length() > 8)
                hex = hex.substring(hex.length() - 8);
            while (hex.length() < 8)
                hex = "0" + hex;
            return hex;
        }
        return null;
    }

    /***************************************************************************
     * Given an IP address stored as a 32-bit integer, converts to dot quad.
     * 
     * @param ip
     *            The IP address as a 32-bit integer
     * @return The IP address as a dot quad
     */
    public static String convertLongToIP(long ip) {
        String ipStr = Long.toHexString(ip);
        while (ipStr.length() < 8)
            ipStr = "0" + ipStr;
        return convertHexToIP(ipStr);
    }

    /***************************************************************************
     * Given an IP represnted as a dot quad, returns a 32-bit integer
     * representation cast into a long.
     * 
     * @param ip
     *            The IP address as a dot quad (e.g. 1.2.3.4)
     * @return The IP address as a long
     */
    public static long convertIPtoLong(String ip) {
        if (ip.equals("-1"))
            return -1;
        return Long.parseLong(convertIPtoHex(ip), 16);
    }

    /***************************************************************************
     * Given an IP address in hex notation (e.g. 01020304), converts it into dot
     * quad notation (e.g. 1.2.3.4).
     * 
     * @param hex
     *            The IP address in hex notation
     * @return The IP address in dot quad notation
     */
    public static String convertHexToIP(String hex) {
        StringBuffer temp = new StringBuffer();
        temp.append(Integer.parseInt(hex.substring(0, 2), 16));
        if (hex.length() == 2) {
            temp.append(".0.0.0");
            return temp.toString();
        }
        temp.append(".");
        temp.append(Integer.parseInt(hex.substring(2, 4), 16));
        if (hex.length() == 4) {
            temp.append(".0.0");
            return temp.toString();
        }
        temp.append(".");
        temp.append(Integer.parseInt(hex.substring(4, 6), 16));
        if (hex.length() == 6) {
            temp.append(".0");
            return temp.toString();
        }
        temp.append(".");
        temp.append(Integer.parseInt(hex.substring(6), 16));
        return temp.toString();
    }

    /***************************************************************************
     * Given an IP in dot quad notation (e.g. 1.2.3.4) returns each octet as an
     * entry in an array of shorts.
     * 
     * @param ip
     *            The dot quad IP address to convert
     * @return An array of short of length 4 or null on error
     */
    public static short[] convertIPtoOctets(String ip) {
        try {
            StringTokenizer st = new StringTokenizer(ip, ".");
            int numTokens = st.countTokens();
            if (numTokens != 4)
                return null;
            short[] temp = new short[numTokens];
            String val = "";
            for (int i = 0; i < numTokens; i++) {
                val = st.nextToken().trim();
                temp[i] = Short.parseShort(val);
            }
            return temp;
        } catch (Exception E) {
            E.printStackTrace();
        }

        return null;
    }

    /***************************************************************************
     * Given an IP in dot quad notation (e.g. 1.2.3.4) returns the hex
     * representation of that IP (e.g. 01020304).
     * 
     * @param ip
     *            The dot quad IP address to convert
     * @return The hex representation of the given IP
     */
    public static String convertIPtoHex(String ip) {
        short[] ipArray = NetFSEUtil.convertIPtoOctets(ip);
        if (ipArray == null)
            return null;

        String temp = "";
        String hex = "";
        for (int i = 0; i < ipArray.length; i++) {
            hex = Integer.toHexString((int) ipArray[i]);
            if (hex.length() > 2)
                hex = hex.substring(hex.length() - 2);
            while (hex.length() < 2)
                hex = "0" + hex;
            temp = temp + hex;
        }
        return temp;
    }

    /***************************************************************************
     * Runs a UNIX command and waits for completion of the command. Properly
     * removes file descriptors, avoiding several JRE bugs.
     * 
     * @param command
     *            The UNIX shell command to run
     * @return The value returned from the command executed.
     */
    public static int execAndWait(String command) {
        try {
            Process p = Runtime.getRuntime().exec(command);
            int val = p.waitFor();
            try {
                p.getInputStream().close();
                p.getOutputStream().close();
                p.getErrorStream().close();
                p.destroy();
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }

            return val;
        } catch (Exception E) {
            E.printStackTrace();
        }
        return -1;
    }

    public static String convertToHex(String line) {
        StringBuffer temp = new StringBuffer();
        byte[] bytes = line.getBytes();
        String str = "";
        for (int i = 0; i < bytes.length; i++) {
            str = Integer.toHexString(bytes[i]);
            if (str.length() == 1)
                str = "0" + str;
            temp.append(str);
        }
        return temp.toString();
    }

    public static String convertFromHex(String line) {
        byte[] bytes = new byte[line.length() / 2];
        for (int i = 0; i < bytes.length; i++)
            bytes[i] = (byte) Integer.parseInt(line.substring(i * 2, i * 2 + 2), 16);
        return new String(bytes);
    }

    public static String generateHostLookupLink(String ip, Timestamp ts) {
        return ip;
    }

    public static String generatePortLookupLink(int port) {
        return "<a target=\"_blank\" href=\"http://www.portsdb.org/bin/portsdb.cgi?portnumber=" + port
                + "&protocol=ANY&String=\">" + port + "</a>";
    }

    public static String generateProtocol(int proto) {
        String protoStr = "" + proto;
        if (proto == 6)
            protoStr = "TCP";
        else if (proto == 1)
            protoStr = "ICMP";
        else if (proto == 17)
            protoStr = "UDP";
        protoStr = "<a target=\"_blank\" href=\"http://www.faqs.org/rfcs/rfc793.html\">" + protoStr + "</a>";
        return protoStr;
    }

    public static String getProtocolName(int proto) {
        String protoStr = "" + proto;
        if (proto == 6)
            protoStr = "TCP";
        else if (proto == 1)
            protoStr = "ICMP";
        else if (proto == 17)
            protoStr = "UDP";
        return protoStr;
    }

    public static String getHostName() {
        return NetFSE.HOST_NAME;
    }

    public static HashMap processCategoryNames() throws Exception {
        HashMap categories = new HashMap(100);
        LineNumberReader in = new LineNumberReader(new FileReader(System.getProperty("r3000.categories")));
        String line = in.readLine();
        while (line != null) {
            String[] tokens = line.split(" ");
            String catName = "";
            for (int i = 1; i < tokens.length; i++) {
                if (i > 1)
                    catName += " ";
                catName += tokens[i];
            }
            categories.put(tokens[0], catName);
            line = in.readLine();
        }
        in.close();
        return categories;
    }

    public static String webify(String str) {
        int idx = 0;
        while (idx < str.length()) {
            char c = str.charAt(idx);
            switch (c) {
            case '>':
                str = str.substring(0, idx) + "&gt;" + str.substring(idx + 1);
                idx += 4;
                break;
            case '<':
                str = str.substring(0, idx) + "&lt;" + str.substring(idx + 1);
                idx += 4;
                break;
            case '&':
                str = str.substring(0, idx) + "&amp;" + str.substring(idx + 1);
                idx += 5;
                break;
            case ' ':
                str = str.substring(0, idx) + "&nbsp;" + str.substring(idx + 1);
                idx += 6;
                break;
            case '"':
                str = str.substring(0, idx) + "&quot;" + str.substring(idx + 1);
                idx += 6;
                break;
            default:
                idx++;
                break;

            }
        }
        return str;
    }

    public static int getProtocolValue(String protoStr) {
        int proto = -1;
        protoStr = protoStr.toUpperCase();
        try {
            proto = Integer.parseInt(protoStr);
            return proto;
        } catch (Exception E) {

        }

        try {
            if (protoStr.startsWith("PROTO")) {
                String temp = protoStr.substring(5);
                proto = Integer.parseInt(temp);
                return proto;
            }
        } catch (Exception E) {

        }

        if (protoStr.equals("TCP"))
            proto = 6;
        else if (protoStr.equals("ICMP"))
            proto = 1;
        else if (protoStr.equals("UDP"))
            proto = 17;
        return proto;
    }

}